/*
   Copyright 2011 Brook Smith

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
package org.xempot.xform;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xempot.xform.executors.Command;
import org.xempot.xform.executors.Template;
import org.xempot.xform.executors.Xpath;
import org.xempot.xform.executors.XpathCommand;
import org.xempot.xform.executors.XpathIf;
import org.xempot.xform.executors.XpathTemplate;
import org.xml.sax.SAXException;

public class Transform {
	private Map<String, String> templates;
	private final Map<String, List<SnippetComponent>> snippitComMap = new HashMap<String, List<SnippetComponent>>();

	private final XPathFactory xpfactory = XPathFactory.newInstance();
	private final InputStream template;
	private final OutputStream out;
	private final InputStream in;

	private Document document;
	private final ExecutorService executor = Executors.newCachedThreadPool();

	private final Map<Instruction, InstructionProcessor> INSTRUCTIONS = new HashMap<Instruction, InstructionProcessor>();

	public Transform(InputStream template, OutputStream out, InputStream in) {
		super();
		this.template = template;
		this.out = out;
		this.in = in;
	}

	public void applyTemplateSnippit(String name, Node node) throws InstructionException, IOException,
			InterruptedException, ExecutionException {
		// TODO how do I tell if the xpath is relative? I guess they all are?

		if (!snippitComMap.containsKey(name)) {
			throw new InstructionException("The template " + name + " is undefined. (defined templates="
					+ snippitComMap.keySet() + ")");
		}

		for (SnippetComponent snippetComponent : snippitComMap.get(name)) {
			if (snippetComponent.getSubpartType() == SnippitSubpartType.instruction) {
				out.write(INSTRUCTIONS.get(snippetComponent.getInstructionData().getInstruction())
						.process(snippetComponent.getInstructionData(), node, document).getBytes("UTF-8"));
			} else {
				out.write(snippetComponent.getValue().getBytes("UTF-8"));
			}
		}
	}

	public Set<String> getCommands() {
		return new HashSet<String>();
	}

	public Map<Instruction, InstructionProcessor> getInstructions() {
		return INSTRUCTIONS;
	}

	public Set<String> getTemplateNames() {
		return templates.keySet();
	}

	private Document loadDom() throws SAXException, IOException, ParserConfigurationException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(true); // never forget this!
		DocumentBuilder builder = factory.newDocumentBuilder();
		return builder.parse(in);
	}

	public void process() throws ExecutionException, InstructionException, XPathExpressionException, IOException,
			InterruptedException {
		System.currentTimeMillis();
		Future<Document> futureDocument = executor.submit(new Callable<Document>() {
			@Override
			public Document call() throws Exception {
				return loadDom();
			}
		});

		Future<Map<String, String>> futureTemplates = executor.submit(new Callable<Map<String, String>>() {
			@Override
			public Map<String, String> call() throws Exception {
				return TemplateLoader.loadTemplate(template);
			}
		});

		// wait for the tasks to complete
		// calling future.get() blocks
		try {
			templates = futureTemplates.get();
			for (Entry<String, String> snippit : templates.entrySet()) {
				// parse the instructions from the templates
				List<SnippetComponent> snippetComponents = SnippitParser.parseSnippit(snippit.getValue(), this);
				snippitComMap.put(snippit.getKey(), snippetComponents);
			}

			document = futureDocument.get();

			// System.out.println("mod=ms" + (System.currentTimeMillis() -
			// start) );
		} catch (InterruptedException e) {
			throw new ExecutionException(e.toString(), e);
		}
		// all loaded now process
		// don't know if all this is nessasry but I was getting null without it

		INSTRUCTIONS.put(Instruction.command, new Command(this));
		INSTRUCTIONS.put(Instruction.template, new Template(this));
		INSTRUCTIONS.put(Instruction.xpath, new Xpath(this));
		INSTRUCTIONS.put(Instruction.xpathcommand, new XpathCommand(this));
		INSTRUCTIONS.put(Instruction.xpathif, new XpathIf(this));
		INSTRUCTIONS.put(Instruction.xpathtemplate, new XpathTemplate(this));

		executor.shutdown();
		executor.awaitTermination(10000, TimeUnit.DAYS);

		applyTemplateSnippit("main", document.getChildNodes().item(0));
	}

}
